package generator

import (
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/intstr"

	"v8s-controller/internal"
)

type (
	rule struct {
		egress *v1.NetworkPolicyEgressRule
	}
)

var (
	NetworkPolicyMeta = metav1.TypeMeta{
		Kind:       "NetworkPolicy",
		APIVersion: "networking.k8s.io/v1",
	}
)

func generateNetworkPolicies(namespace internal.Namespace) []runtime.Object {
	return []runtime.Object{
		generateNetworkPolicy("isolate-project-policy", allowEgressSameNamespace(namespace)),
		generateNetworkPolicy("allow-dns-policy", allowEgressByPort(53, corev1.ProtocolTCP, corev1.ProtocolUDP)),
		generateNetworkPolicy("allow-all-external-ips", allowEgressExceptCluster()),
	}
}

func generateNetworkPolicy(name string, rules ...rule) *v1.NetworkPolicy {
	egress := make([]v1.NetworkPolicyEgressRule, 0)

	for _, item := range rules {
		if item.egress != nil {
			hold := item.egress
			egress = append(egress, *hold)
		}
	}

	types := make([]v1.PolicyType, 0)
	if len(egress) > 0 {
		types = append(types, v1.PolicyTypeEgress)
	}

	return &v1.NetworkPolicy{
		TypeMeta:   NetworkPolicyMeta,
		ObjectMeta: metav1.ObjectMeta{Name: name},
		Spec: v1.NetworkPolicySpec{
			Egress:      egress,
			PolicyTypes: types,
		},
	}
}

func allowEgressSameNamespace(namespace internal.Namespace) rule {
	return rule{
		egress: &v1.NetworkPolicyEgressRule{
			To: []v1.NetworkPolicyPeer{
				{
					NamespaceSelector: &metav1.LabelSelector{
						MatchLabels: map[string]string{
							projectLabel: string(namespace),
						},
					},
				},
			},
		},
	}
}

func allowEgressByPort(port int, protocols ...corev1.Protocol) rule {
	ports := make([]v1.NetworkPolicyPort, 0, len(protocols))

	for _, protocol := range protocols {
		proto := protocol
		ontOrStr := intstr.FromInt(port)

		ports = append(ports, v1.NetworkPolicyPort{
			Protocol: &proto,
			Port:     &ontOrStr,
		})
	}

	return rule{egress: &v1.NetworkPolicyEgressRule{Ports: ports}}
}

func allowEgressExceptCluster() rule {
	return rule{egress: &v1.NetworkPolicyEgressRule{
		To: []v1.NetworkPolicyPeer{
			{
				IPBlock: &v1.IPBlock{
					CIDR:   "0.0.0.0/0",
					Except: []string{"10.0.0.0/8"},
				},
			},
		},
	}}
}
